/**
 * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
 * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
 */
/**
 * @module engine/dataprocessor/xmldataprocessor
 */
/* globals DOMParser */
import BasicHtmlWriter from './basichtmlwriter';
import DomConverter from '../view/domconverter';
/**
 * The XML data processor class.
 * This data processor implementation uses XML as input and output data.
 * This class is needed because unlike HTML, XML allows to use any tag with any value.
 * For example, `<link>Text</link>` is a valid XML but invalid HTML.
 */
export default class XmlDataProcessor {
    /**
     * Creates a new instance of the XML data processor class.
     *
     * @param document The view document instance.
     * @param options Configuration options.
     * @param options.namespaces A list of namespaces allowed to use in the XML input.
     */
    constructor(document, options = {}) {
        this.skipComments = true;
        this.namespaces = options.namespaces || [];
        this.domParser = new DOMParser();
        this.domConverter = new DomConverter(document, { renderingMode: 'data' });
        this.htmlWriter = new BasicHtmlWriter();
    }
    /**
     * Converts the provided {@link module:engine/view/documentfragment~DocumentFragment document fragment}
     * to data format &mdash; in this case an XML string.
     *
     * @returns An XML string.
     */
    toData(viewFragment) {
        // Convert view DocumentFragment to DOM DocumentFragment.
        const domFragment = this.domConverter.viewToDom(viewFragment);
        // Convert DOM DocumentFragment to XML output.
        // There is no need to use dedicated for XML serializing method because BasicHtmlWriter works well in this case.
        return this.htmlWriter.getHtml(domFragment);
    }
    /**
     * Converts the provided XML string to a view tree.
     *
     * @param data An XML string.
     * @returns A converted view element.
     */
    toView(data) {
        // Convert input XML data to DOM DocumentFragment.
        const domFragment = this._toDom(data);
        // Convert DOM DocumentFragment to view DocumentFragment.
        return this.domConverter.domToView(domFragment, {
            keepOriginalCase: true,
            skipComments: this.skipComments
        });
    }
    /**
     * Registers a {@link module:engine/view/matcher~MatcherPattern} for view elements whose content should be treated as raw data
     * and not processed during the conversion from XML to view elements.
     *
     * The raw data can be later accessed by a
     * {@link module:engine/view/element~Element#getCustomProperty custom property of a view element} called `"$rawContent"`.
     *
     * @param pattern Pattern matching all view elements whose content should be treated as raw data.
     */
    registerRawContentMatcher(pattern) {
        this.domConverter.registerRawContentMatcher(pattern);
    }
    /**
     * If the processor is set to use marked fillers, it will insert `&nbsp;` fillers wrapped in `<span>` elements
     * (`<span data-cke-filler="true">&nbsp;</span>`) instead of regular `&nbsp;` characters.
     *
     * This mode allows for a more precise handling of block fillers (so they do not leak into editor content) but
     * bloats the editor data with additional markup.
     *
     * This mode may be required by some features and will be turned on by them automatically.
     *
     * @param type Whether to use the default or the marked `&nbsp;` block fillers.
     */
    useFillerType(type) {
        this.domConverter.blockFillerMode = type == 'marked' ? 'markedNbsp' : 'nbsp';
    }
    /**
     * Converts an XML string to its DOM representation. Returns a document fragment containing nodes parsed from
     * the provided data.
     */
    _toDom(data) {
        // Stringify namespaces.
        const namespaces = this.namespaces.map(nsp => `xmlns:${nsp}="nsp"`).join(' ');
        // Wrap data into root element with optional namespace definitions.
        data = `<xml ${namespaces}>${data}</xml>`;
        const parsedDocument = this.domParser.parseFromString(data, 'text/xml');
        // Parse validation.
        const parserError = parsedDocument.querySelector('parsererror');
        if (parserError) {
            throw new Error('Parse error - ' + parserError.textContent);
        }
        const fragment = parsedDocument.createDocumentFragment();
        const nodes = parsedDocument.documentElement.childNodes;
        while (nodes.length > 0) {
            fragment.appendChild(nodes[0]);
        }
        return fragment;
    }
}
